/*
 * Amadeus Confidential Information:
 * Unauthorized use and disclosure strictly forbidden.
 * @1998-2009 - Amadeus s.a.s - All Rights Reserved.
 */
package com.amadeus.spin.devtool.soap.shooter.process;

import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.log4j.Logger;
import org.w3c.dom.Node;

import com.amadeus.spin.devtool.soap.shooter.FatalException;
import com.amadeus.spin.devtool.soap.shooter.ProcessInput;
import com.amadeus.spin.devtool.soap.shooter.config.ConfigurationParameters;

/**
 * ReplaceSchema is called for each Soap request if Shooter has been runned with option -uss (or -useserverschema) It
 * will try to replace schema defined in the soap request with the schema present on targeted server
 *
 * @author DEV/SEP/QAD/NRE/EAI
 */
public class ReplaceSchema implements ProcessInput {

  private static final String SCHEMA_ATTRIBUTE = "noNamespaceSchemaLocation";

  /**
   * Regexp to find version and path to schema :
   * - V(\\d+(?:\\.)?\\d+)[^-:/]* : search for version ( like V14.2 V122 atlv122)
   * - (?:/xmlschemas?)* : "xmlschema" and "xmlschemas" strings should not be included in the path to xsd
   * - (/.*\\.xsd) : path to xsd
   */
  private static final Pattern PATTERN = Pattern.compile(
      "V(\\d+(?:\\.)?\\d+(?:\\.)?\\d?[A-Za-z]*\\d?)[^-:/]*(?:/xmlschemas?)?(/.*\\.xsd)", Pattern.CASE_INSENSITIVE);

  private static final String VERSION_ATTRIBUTE = "Version=";

  private static Logger log = Logger.getLogger(ReplaceSchema.class);

  private String computedVersion = "";

  private int namespaceIndex = 0;

  @Override
  public String processInput(Stack<String> previousOutput, String input, Node config) throws FatalException {
    StringBuilder xml = replaceNamespaceServer(input);
    return replaceVersionAttribute(xml);
  }

  /**
   * Replaces the schema in the namespace attribute
   *
   * @param input
   * @return
   */
  private StringBuilder replaceNamespaceServer(String input) {
    // remove comments, which makes the following schema replacement
    // fail when the comment contains an old schema reference, as only
    // the first occurrence is replaced
    input = input.replaceAll("(?s)<!--.*?-->", "");

    StringBuilder xml = new StringBuilder(input);
    int start = xml.indexOf(SCHEMA_ATTRIBUTE);
    String xmlSchemaUrl = null;
    int end = -1;

    if (start > 0) {
      start = xml.indexOf("\"", start) + 1;
      end = xml.indexOf("\"", start);
    }
    namespaceIndex = start;

    try {
      String server = ConfigurationParameters.getServer();
      xmlSchemaUrl = input.substring(start, end);
      String cleanXmlSchemaUrl = xmlSchemaUrl.replaceAll("\\\\+", "/");

      // Process cases that was nearly impossible to describe in regexp
      cleanXmlSchemaUrl = cleanXmlSchemaUrl.replace("nceetvdev", "nceetvdev_");
      cleanXmlSchemaUrl = cleanXmlSchemaUrl.replaceAll("/(\\d+(?:\\.?\\d+)*)/", "/V$1/");
      cleanXmlSchemaUrl = cleanXmlSchemaUrl.replaceAll("^(\\d+(?:\\.?\\d+)*)/", "/V$1/");

      // Apply pattern on original schema url to find version and schema name
      Matcher matcher = PATTERN.matcher(cleanXmlSchemaUrl);
      matcher.find();
      String version = matcher.group(1);
      String path = matcher.group(2);

      // Generate the new schema url
      StringBuilder targetSchemaUrl = new StringBuilder();
      if (server.indexOf("://") == -1) {
        targetSchemaUrl.append("http://");
      }
      targetSchemaUrl.append(server).append("/XmlSchemas/V").append(version);

      computedVersion = targetSchemaUrl.substring(targetSchemaUrl.length() - version.length());
      if (version.indexOf(".") == -1) {
        targetSchemaUrl.insert(targetSchemaUrl.length() - 1, '.');
        computedVersion = targetSchemaUrl.substring(targetSchemaUrl.length() - 1 - version.length());
      }
      targetSchemaUrl.append(path);
      String targetUrl = targetSchemaUrl.toString();

      if (log.isInfoEnabled()) {
        log.info("Original schema url has been replaced schema url from targeted server ");
        log.info("  \"" + xmlSchemaUrl + "\"");
        log.info("  => \"" + targetUrl + "\"");
      }

      // Replace the schema url
      xml.replace(start, end, targetUrl);
      return xml;
    }
    catch (Exception e) {
      // reach condition : url cannot be understood or any technical exception - original url is not modified
      StringBuilder sb = new StringBuilder("Unable to replace schema url, ");

      if (null == xmlSchemaUrl) {
        sb.append("attribute xsi:noNamespaceSchemaLocation is missing");
      }
      else {
        sb.append("original url cannot be processed : \"" + xmlSchemaUrl + "\"");
      }

      log.warn(sb.toString());
      return new StringBuilder(input);
    }
  }

  /**
   * Replaces the version="" attribute to match the version modification done in replaceNamespaceServer()
   *
   * @param xml
   * @return
   */
  private String replaceVersionAttribute(StringBuilder xml) {
    String newXmlVersionField = null;

    // Indexes for the version field
    int startVersion = xml.indexOf(VERSION_ATTRIBUTE, namespaceIndex);
    if (startVersion == -1) {
      // Cases where we had version instead of Version.
      startVersion = xml.indexOf(VERSION_ATTRIBUTE.toLowerCase(), namespaceIndex);
    }
    if (startVersion > 0 && computedVersion != null && computedVersion.length() > 0) {
      int endVersion = -1;
      startVersion = xml.indexOf("\"", startVersion) + 1;
      endVersion = xml.indexOf("\"", startVersion);
      // Construct the new Version field.
      String xmlVersionField = xml.substring(startVersion, endVersion);

      newXmlVersionField = "V".concat(computedVersion);

      if (log.isInfoEnabled() && !xmlVersionField.equals(newXmlVersionField)) {
        log.info("Original Version field has been replaced ");
        log.info("  \"" + VERSION_ATTRIBUTE + "\"" + xmlVersionField + "\"\"");
        log.info("  => \"" + VERSION_ATTRIBUTE + "\"" + newXmlVersionField + "\"\"");
      }

      // Replace the version field
      xml.replace(startVersion, endVersion, newXmlVersionField);
    }

    return xml.toString();
  }
}